package ru.niir.goodfil.ui;

import org.apache.log4j.Logger;
import ru.niir.goodfil.audit.domain.ReplacementUIEvent;
import ru.niir.goodfil.audit.domain.enums.Crud;
import ru.niir.goodfil.db.Replacement;
import ru.niir.goodfil.db.Role;
import ru.niir.goodfil.db.User;
import ru.niir.goodfil.exceptions.CreateFailureException;
import ru.niir.goodfil.exceptions.GoodfilSecurityException;
import ru.niir.goodfil.exceptions.RemoveFailureException;
import ru.niir.goodfil.exceptions.ValidationException;
import ru.niir.goodfil.ui.utils.PageIndex;
import ru.niir.goodfil.utils.filters.ReplacementsFilter;
import ru.niir.goodfil.utils.validation.CreateCheckResult;
import ru.niir.goodfil.utils.validation.ValidationError;
import ru.niir.goodfil.utils.validation.ValidationResult;

import javax.faces.model.SelectItem;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Замещение
 *
 * @author
 */
public class ReplacementsBean extends PageBean {
    private static final Logger logger = Logger.getLogger(ReplacementsBean.class);

    /**
     * Название настройки, задающей размер страницы
     */
    private static final String REPLACEMENTS_PAGE_SIZE_KEY = ".replacements.page.size";
    /**
     * Название настройки, задающей отступ от текущей страницы влево для пэйджинатора
     */
    private static final String REPLACEMENTS_LEFT_PAGE_OFFSET_SIZE = ".replacements.page.left.offset";
    /**
     * Название настройки, задающей отступ от текущей страницы вправо для пэйджинатора
     */
    private static final String REPLACEMENTS_RIGHT_PAGE_OFFSET_SIZE = ".replacements.page.right.offset";

    /**
     * Количество страниц (с учетом фильтра)
     */
    private int pagesCount = -1;
    /**
     * Количество замещенных менеджеров (с учетом фильтра)
     */
    private int replacementsCount = -1;
    /**
     * Размер страницы
     */
    private int pageSize = -1;
    /**
     * Отступ от текущей страницы влево для паджинатора (кол-во страниц, слева от текущей)
     */
    private int _leftPageOffset = -1;
    /**
     * Отступ от текущей страницы вправо для паджинатора (кол-во страниц, справа от текущей)
     */
    private int _rightPageOffset = -1;

    /**
     * Замещение
     */
    private Replacement replacement = new Replacement();
    /**
     * Идентификатор замещения
     */
    private long replacementId = -1;

    public ReplacementsBean() {
        if (!getSecurityBean().getIsRoleAdministrator())
            throw new GoodfilSecurityException("Только администраторы имеют доступ к управлению замещениями менеджеров");
    }

    /**
     * Возвращает (согласно настройкам) размер страницы (для пэйджинатора)
     *
     * @return размер страницы
     */
    public int getPageSize() {
        gf().createIfNotExists(REPLACEMENTS_PAGE_SIZE_KEY, 10);
        if (pageSize == -1) pageSize = gf().getInt(REPLACEMENTS_PAGE_SIZE_KEY);
        return pageSize;
    }

    /**
     * Возвращает (согласно настройкам) размер отступа влево (в страницах) для пэйджинатора
     *
     * @return размер отступа
     */
    public int getLeftPaggeOffset() {
        gf().createIfNotExists(REPLACEMENTS_LEFT_PAGE_OFFSET_SIZE, 5);
        if (_leftPageOffset == -1) _leftPageOffset = gf().getInt(REPLACEMENTS_LEFT_PAGE_OFFSET_SIZE);
        return _leftPageOffset;
    }

    /**
     * Возвращает (согласно настройкам) размер отступа вправо (в страницах) для пэйджинатора
     *
     * @return размер отступа
     */
    public int getRightPaggeOffset() {
        gf().createIfNotExists(REPLACEMENTS_RIGHT_PAGE_OFFSET_SIZE, 5);
        if (_rightPageOffset == -1) _rightPageOffset = gf().getInt(REPLACEMENTS_RIGHT_PAGE_OFFSET_SIZE);
        return _rightPageOffset;
    }

    /**
     * Получить количество товаров (с учетом фильтра)
     *
     * @return
     */
    public int getReplacementsCount() {
        if (replacementsCount == -1)
            replacementsCount = gf().getReplacementsCount(getSessionBean().getReplacementsFilter());
        return replacementsCount;
    }

    /**
     * Возвращает количество страниц (с учетом фильтра)
     *
     * @return количество страниц
     */
    public int getPagesCount() {
        if (pagesCount == -1) {
            pagesCount = getReplacementsCount() / getPageSize() + 1;
        }
        return pagesCount;
    }

    /**
     * Перейти к началу (на первую страницу)
     */
    public void toBegin() {
        applyPage();
        setPage(1);
    }

    /**
     * Перейти к концу (на последнюю страницу)
     */
    public void toEnd() {
        applyPage();
        setPage(getPagesCount());
    }

    /**
     * Отступить на несколько страниц назад
     */
    public void longPrevPage() {
        applyPage();
        int p = getPage() - getLeftPaggeOffset() - 1;
        if (p < 1) p = 1;
        setPage(p);
    }

    /**
     * Отступить на несколько страниц вперед
     */
    public void longNextPage() {
        applyPage();
        int p = getPage() + getRightPaggeOffset() + 1;
        if (p > getPagesCount()) p = getPagesCount();
        setPage(p);
    }

    /**
     * Перейти на страницу вперед
     */
    public void nextPage() {
        applyPage();
        if (getPage() < getPagesCount()) setPage(getPage() + 1);
    }

    /**
     * Перейти на страницу назад
     */
    public void prevPage() {
        applyPage();
        if (getPage() > 1) setPage(getPage() - 1);
    }

    /**
     * Возвращает номер текущей страницы
     *
     * @return номер страници
     */
    public int getPage() {
        return getSessionBean().getReplacementsPage();
    }

    /**
     * Устанавливает номер текущей страницы.
     *
     * @param page номер страници
     */
    public void setPage(int page) {
        getSessionBean().setReplacementsPage(page);
    }

    /**
     * Применить текущую страницу
     */
    public void applyPage() {
        pagesCount = -1;
        replacementsCount = -1;
    }

    /**
     * Возвращает номера страниц для отрисовки в пэйджинаторе
     *
     * @return номера страниц
     */
    public List<PageIndex> getPageIndexes() {

        int minPage = getPage() - getLeftPaggeOffset();
        if (minPage < 1) minPage = 1;

        int maxPage = minPage + getLeftPaggeOffset() + getRightPaggeOffset();
        if (maxPage > getPagesCount()) maxPage = getPagesCount();

        List<PageIndex> result = new ArrayList<PageIndex>();
        for (int i = minPage; i <= maxPage; i++)
            result.add(new PageIndex(i, i == getPage()));

        return result;
    }

    /**
     * Применить фильтр
     */
    public void applyFilter() {
        setPage(1);
        replacementsCount = -1;
        pagesCount = -1;
    }

    /**
     * Возвращает номер первой страницы, которую необходимо отрисовать в пэйджинаторе.
     *
     * @return номер первой страницы
     */
    private int getPageStart() {
        int pageStart = getPage() * getPageSize() - getPageSize();
        if (pageStart < 0) pageStart = 0;
        return pageStart;
    }

    /**
     * Возвращает номер последней страницы, которую необходимо отрисовать в пэйджинаторе
     *
     * @return номер последней страницы
     */
    private int getPageEnd() {
        int pageEnd = getPageStart() + getPageSize();
        if (pageEnd > getReplacementsCount()) pageEnd = getReplacementsCount();
        return pageEnd;
    }

    /**
     * Возвращает перечень замещений для отображения, с учетом фильтра и номера текущей страницы.
     *
     * @return перечень замещений
     */
    public List<Replacement> getReplacements() {
        logger.debug("getReplacements: " + getSessionBean().getReplacementsFilter());

        List<Replacement> replacements = gf().getReplacements(getPageStart(), getPageSize(), getSessionBean().getReplacementsFilter());
        List<User> users = gf().getUsersByRoleId(Role.MANAGER);
        for (Replacement r : replacements) {
            long m = r.getManagerId();
            long a = r.getAssistantId();
            for (User u : users) {
                if (m == u.getId()) r.setManager(u);
                if (a == u.getId()) r.setAssistant(u);
            }
        }
        return replacements;
    }

    /**
     * Возвращает информацию о замещении.
     *
     * @return замещение
     */
    public Replacement getReplacement() {
        if (replacement.getId() != replacementId) {
            if (replacementId == -1) replacement = new Replacement();
            else {
                replacement = gf().getReplacementById(replacementId);

                replacement.setManager(gf().getUserById(replacement.getManagerId()));
                replacement.setAssistant(gf().getUserById(replacement.getAssistantId()));
            }
        }

        return replacement;
    }

    /**
     * Устанавливает информацию о замещении.
     *
     * @param replacement замещение
     */
    public void setReplacement(Replacement replacement) {
        this.replacement = replacement;
    }

    /**
     * Возвращает идентификатор выбранного замещения.
     *
     * @return идентификатор замещения
     */
    public long getReplacementId() {
        return replacementId;
    }

    /**
     * Устанавливает идентификатор выбранного замещения.
     *
     * @param replacementId идентификатор замещения
     */
    public void setReplacementId(long replacementId) {
        this.replacementId = replacementId;
    }

    /**
     * Возвращает список пользователей
     *
     * @return список пользователей
     */
    public List<SelectItem> getUsersList() {
        List<SelectItem> result = new ArrayList<SelectItem>();
        List<User> users = gf().getUsersByRoleId(2);
        result.add(new SelectItem(-1, "Не установлен"));
        for (User user : users) result.add(new SelectItem(user.getId(), user.getFio()));
        return result;
    }

    /**
     * UNSAFE
     * Возвращает список заменяемых менеджеров.
     *
     * @return список менеджеров
     */
    public List<SelectItem> getReplacementManager() {
        List<SelectItem> result = new ArrayList<SelectItem>();
        result.add(new SelectItem(-1, "Не установлен"));
        List<User> users = gf().getUsersByRoleId(Role.MANAGER);

        for (User u : users) {
            result.add(new SelectItem(u.getId(), u.getFio()));
        }
        return result;
    }

    /**
     * Возвращает список заменяеыми менеджерами.
     *
     * @return список менеджеров.
     */
    public List<SelectItem> getReplacementAssistant() {
        List<SelectItem> result = new ArrayList<SelectItem>();
        result.add(new SelectItem(-1, "Не установлен"));

        List<User> users = gf().getUsersByRoleId(Role.MANAGER);
        for (User user : users) result.add(new SelectItem(user.getId(), user.getFio()));
        return result;
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Работа с БД----------
     * -------------------------------------------------------------------------
     */

    /**
     * Добаление замещения
     */
    public void addReplacement() {
        if (replacement.getAssistantId() == -1) {
            facesError("Не выбран замещающий менеджер");
            return;
        }

        if (replacement.getManagerId() == -1) {
            facesError("Не выбран замещаемый менеджер");
            return;
        }

        if (replacement.getManagerId() == replacement.getAssistantId()) {
            facesError("Менеджер не может замещать сам себя");
            return;
        }

        /*
        Самая простая проверка: данные менеджеры не замещают друг друга с пересечением дат
         */
        long L1 = replacement.getBegdate() == null ? 0 : replacement.getBegdate().getTime();
        long L2 = replacement.getEnddate() == null ? Long.MAX_VALUE : replacement.getEnddate().getTime();

        ReplacementsFilter replacementsFilter = new ReplacementsFilter();

        Set<Replacement> replacements = new HashSet<Replacement>();

        replacementsFilter.setManagerId(replacement.getManagerId());
        replacementsFilter.setAssistanceId(-1);
        replacements.addAll(gf().getReplacements(0, Integer.MAX_VALUE, replacementsFilter));

        replacementsFilter.setManagerId(replacement.getAssistantId());
        replacementsFilter.setAssistanceId(-1);
        replacements.addAll(gf().getReplacements(0, Integer.MAX_VALUE, replacementsFilter));

        replacementsFilter.setManagerId(-1);
        replacementsFilter.setAssistanceId(replacement.getAssistantId());
        replacements.addAll(gf().getReplacements(0, Integer.MAX_VALUE, replacementsFilter));

        replacementsFilter.setManagerId(-1);
        replacementsFilter.setAssistanceId(replacement.getManagerId());
        replacements.addAll(gf().getReplacements(0, Integer.MAX_VALUE, replacementsFilter));


        logger.debug("Replacement check: L1=" + L1 + ", L2=" + L2);
        for (Replacement r : replacements) {
            //  Очень простой фикс: замещение не нужно сравнивать само с собой.
            //  Это важно в случае, когда мы редактируем, а не создаем замещение
            if (r.getId() == replacement.getId()) {
                continue;
            }

            long l1 = r.getBegdate() == null ? 0 : r.getBegdate().getTime();
            long l2 = r.getEnddate() == null ? Long.MAX_VALUE : r.getEnddate().getTime();

            logger.debug("..l1=" + l1 + ", l2=" + l2);

            if (Utils.intersecs(L1, L2, l1, l2)) {
                logger.debug("....catch!");
                facesError("Даты замещения пересекаются с существующими замещениями указанных менеджеров");
                break;
            }
        }

        if (hasFacesErrors()) return;

        if (replacement.getId() == -1) {
            try {
                gf().saveReplacement(replacement);
                getAuditService().logReplacementUIEvent(prepareReplacementsEvent(replacement, Crud.CREATE));
                facesInfo("Замещение создано");
            } catch (ValidationException ex) {
                logger.error(ex);
                ex.printStackTrace();
                ValidationResult validationResult = ex.getResult();
                for (ValidationError error : validationResult.getErrors())
                    facesError(error.toHumanString());
            } catch (CreateFailureException ex) {
                logger.error(ex);
                ex.printStackTrace();
                CreateCheckResult createCheckResult = ex.getResult();
                facesError(createCheckResult.getMsg());
            }
        } else {
            try {
                gf().updateReplacement(replacement);
                getAuditService().logReplacementUIEvent(prepareReplacementsEvent(replacement, Crud.UPDARE));
                facesInfo("Замещение изменено");
            } catch (ValidationException ex) {
                logger.error(ex);
                ex.printStackTrace();
                ValidationResult validationResult = ex.getResult();
                for (ValidationError error : validationResult.getErrors())
                    facesError(error.toHumanString());
            }
        }
    }

    /**
     * Удаление данных о товаре
     */
    public void removeReplacement() {
        try {
            ReplacementUIEvent event = prepareReplacementsEvent(replacement, Crud.DELETE);
            gf().removeReplacement(replacement.getId());
            getAuditService().logReplacementUIEvent(event);
            facesInfo("Замещение удалено");
        } catch (RemoveFailureException ex) {
            logger.error(ex);
            ex.printStackTrace();
            String[] objs = ex.getRemoveCheckResult().getLinkedObjects();
            for (String obj : objs) {
                facesError("Заказ нельзя удалить, т.к. он связан с другими объектами.");
                break;
            }
        }
    }

    private ReplacementUIEvent prepareReplacementsEvent(Replacement replacement, Crud crud) {
        User manager = gf().getUserById(replacement.getManagerId());
        User assistant = gf().getUserById(replacement.getAssistantId());

        ReplacementUIEvent event = getAuditHelper().createEvent(ReplacementUIEvent.class);
        event.setCrud(crud);
        event.setReplacementId(String.valueOf(replacement.getId()));
        event.setAssistantId(String.valueOf(replacement.getAssistantId()));
        event.setManagerId(String.valueOf(replacement.getManagerId()));

        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
        if (replacement.getBegdate() != null) {
            event.setBegdate(sdf.format(replacement.getBegdate()));
        }
        if (replacement.getEnddate() != null) {
            event.setEnddate(sdf.format(replacement.getEnddate()));
        }

        event.setManagerName(manager.getFio());
        event.setAssistantName(assistant.getFio());

        return event;
    }
}
